﻿using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using Screen = System.Windows.Forms.Screen;

namespace System.Windows
{
    public static class WindowExtensions
    {
        #region native

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        /// <summary>
        /// SetWindowPos options
        /// </summary>
        [Flags]
        public enum SWP
        {
            ASYNCWINDOWPOS = 0x4000,
            DEFERERASE = 0x2000,
            DRAWFRAME = 0x0020,
            FRAMECHANGED = 0x0020,
            HIDEWINDOW = 0x0080,
            NOACTIVATE = 0x0010,
            NOCOPYBITS = 0x0100,
            NOMOVE = 0x0002,
            NOOWNERZORDER = 0x0200,
            NOREDRAW = 0x0008,
            NOREPOSITION = 0x0200,
            NOSENDCHANGING = 0x0400,
            NOSIZE = 0x0001,
            NOZORDER = 0x0004,
            SHOWWINDOW = 0x0040,

            TOPMOST = NOACTIVATE | NOOWNERZORDER | NOSIZE | NOMOVE | NOREDRAW | NOSENDCHANGING
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SWP uFlags);

        /// <summary>
        /// ShowWindow options
        /// </summary>
        public enum SW
        {
            HIDE = 0,
            SHOWNORMAL = 1,
            NORMAL = 1,
            SHOWMINIMIZED = 2,
            SHOWMAXIMIZED = 3,
            MAXIMIZE = 3,
            SHOWNOACTIVATE = 4,
            SHOW = 5,
            MINIMIZE = 6,
            SHOWMINNOACTIVE = 7,
            SHOWNA = 8,
            RESTORE = 9,
            SHOWDEFAULT = 10,
            FORCEMINIMIZE = 11,
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool ShowWindow(IntPtr hwnd, SW nCmdShow);

        [StructLayout(LayoutKind.Sequential)]
        public struct SIZE
        {
            public int cx;
            public int cy;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

        [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
        [StructLayout(LayoutKind.Sequential)]
        public class RECT
        {
            private int _left;
            private int _top;
            private int _right;
            private int _bottom;

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public RECT() : this(0, 0, 0, 0)
            {
            }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public RECT(int left, int top, int right, int bottom)
            {
                _left = left;
                _top = top;
                _right = right;
                _bottom = bottom;
            }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public int Width
            {
                get { return _right - _left; }
            }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public int Height
            {
                get { return _bottom - _top; }
            }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public int Left
            {
                get { return _left; }
                set { _left = value; }
            }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public int Right
            {
                get { return _right; }
                set { _right = value; }
            }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public int Top
            {
                get { return _top; }
                set { _top = value; }
            }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public int Bottom
            {
                get { return _bottom; }
                set { _bottom = value; }
            }

            [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
            public void Offset(int dx, int dy)
            {
                _left += dx;
                _top += dy;
                _right += dx;
                _bottom += dy;
            }
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class MONITORINFO
        {
            public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
            public RECT rcMonitor = new RECT();
            public RECT rcWork = new RECT();
            public int dwFlags = 0;
        }

        public enum MonitorOptions : uint
        {
            MONITOR_DEFAULTTONULL = 0x00000000,
            MONITOR_DEFAULTTOPRIMARY = 0x00000001,
            MONITOR_DEFAULTTONEAREST = 0x00000002
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [DllImport("user32.dll")]
        public static extern IntPtr MonitorFromWindow(IntPtr hwnd, MonitorOptions dwFlags);

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [DllImport("user32.dll", EntryPoint = "GetMonitorInfo", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetMonitorInfo(IntPtr hMonitor, [In, Out] MONITORINFO lpmi);

        private static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
        private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
        private static readonly IntPtr HWND_TOP = new IntPtr(0);
        private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);

        #endregion native

        private static void MoveWindow(this Window w, int x, int y, int cx, int cy)
        {
            var handle = new WindowInteropHelper(w).Handle;

            if (w.WindowStyle == WindowStyle.None)
            {
                cy = Screen.PrimaryScreen.Bounds.Height;
                //var trayHWND = FindWindow("Shell_TrayWnd", null);
                //SetWindowPos(handle, trayHWND, x, y, cx, cy, SWP.SHOWWINDOW);
                SetWindowPos(handle, HWND_TOPMOST, x, y, cx, cy, SWP.SHOWWINDOW);
            }
            else
            {
                cy = Screen.PrimaryScreen.WorkingArea.Height;
                SetWindowPos(handle, HWND_NOTOPMOST, x, y, cx, cy, SWP.SHOWWINDOW);
            }
        }

        public static int GetScreensCount(this Window w)
        {
            return Screen.AllScreens.Length;
        }

        public static void SetCrossMonitor(this Window w, int count)
        {
            int x = Screen.PrimaryScreen.WorkingArea.Left;
            int y = Screen.PrimaryScreen.WorkingArea.Top;

            int cx = 0;         //Screen.PrimaryScreen.WorkingArea.Width;
            int cy = 0;    //Screen.PrimaryScreen.WorkingArea.Height;

            count = Math.Max(1, count);

            foreach (Screen s in Screen.AllScreens)
            {
                if (count-- > 0)
                    cx += s.Bounds.Width;
            }

            w.MoveWindow(x, y, cx, cy);
        }

        public static void MoveToScreen(this Window w, int idxScreen)
        {
            if (idxScreen < 0 || idxScreen >= Screen.AllScreens.Length)
                return;

            var screen = Screen.AllScreens[idxScreen];

            int x = screen.WorkingArea.Left;
            int y = screen.WorkingArea.Top;

            int cx = screen.Bounds.Width;
            int cy = screen.Bounds.Height;

            w.MoveWindow(x, y, cx, cy);
        }

        public static void SetFullScreenOn(this Window w, int idxScreen)
        {
            if (idxScreen < 0 || idxScreen >= Screen.AllScreens.Length)
                return;

            w.ResizeMode = ResizeMode.NoResize;
            w.WindowStyle = WindowStyle.None;

            var screen = Screen.AllScreens[idxScreen];

            int x = screen.WorkingArea.Left;
            int y = screen.WorkingArea.Top;

            int cx = screen.Bounds.Width;
            int cy = screen.Bounds.Height;

            w.MoveWindow(x, y, cx, cy);
        }

        public static void ResetFullScreenOn(this Window w)
        {
            w.WindowStyle = WindowStyle.SingleBorderWindow;
            w.ResizeMode = ResizeMode.CanResize;
            w.WindowState = WindowState.Normal;
        }

        public static void SetFullScreen(this Window w, int count = 1)
        {
            w.ResizeMode = ResizeMode.NoResize;
            w.WindowStyle = WindowStyle.None;
            w.SetCrossMonitor(count);
        }

        public static void ResetFullScreen(this Window w, int count = 1)
        {
            w.WindowStyle = WindowStyle.SingleBorderWindow;
            w.ResizeMode = ResizeMode.CanResize;
            w.WindowState = WindowState.Normal;

            w.SetCrossMonitor(count);
        }
    }
}